package io.github.codelifeleno.jdbrelease.execute;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.resource.Resource;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.util.StrUtil;
import io.github.codelifeleno.jdbrelease.config.DBScriptConfig;
import io.github.codelifeleno.jdbrelease.config.JDBReleaseConfig;
import io.github.codelifeleno.jdbrelease.model.DBScriptRunModel;
import io.github.codelifeleno.jdbrelease.model.JDBReleaseRunModel;
import io.github.codelifeleno.jdbrelease.model.finals.DefaultName;
import io.github.codelifeleno.jdbrelease.model.finals.JDBReleaseRunStatus;
import io.github.codelifeleno.jdbrelease.util.JDBResourceUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.sql.DataSource;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.FileSystemException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

public class JDBReleaseService {

    private static final Logger log = LoggerFactory.getLogger(JDBReleaseService.class);
    public static Path workingDirPath = Paths.get(System.getProperty("user.dir")).resolve("JDBRelease");
    private final JDBReleaseConfig jdbcReleaseConfig;

    public JDBReleaseService(JDBReleaseConfig jdbcReleaseConfig) {
        this.jdbcReleaseConfig = jdbcReleaseConfig;
    }


    /**
     * 根据脚本目录以及运行目录执行其中的脚本文件
     * 文件筛选按照既定的规则进行
     * </br>* 需要满足 5 个条件
     * <ol>
     * <li>  不能是 DBScriptConfig.xml 文件
     * <li>  不能是 包含 .jdbrelease 的路径
     * <li>  需要满足配置的正则表达式
     * <li>  需要是首字母为 默认的要执行的文件前缀字符串
     * <li>  去掉已经执行过的脚本文件
     * </ol>
     * 对于其他每次都需要执行的文件不筛选已经执行过的脚本文件
     *
     * @param scriptDir  脚本目录
     * @param runningDir 运行日志目录
     * @return 是否执行成功
     */
    public boolean execute(String scriptDir, File runningDir, DataBaseService dbService) throws IOException, URISyntaxException {

        DBScriptConfigInit dbScriptConfigInit = new DBScriptConfigInit();
        dbScriptConfigInit.configInit(ResourceUtil.getResourceObj(scriptDir));
        /* 筛选第一部分要执行的文件
         * 需要满足 5 个条件
         * 1. 不能是 DBScriptConfig.xml 文件
         * 2. 不能是 包含 .jdbrelease 的路径
         * 3. 需要满足配置的正则表达式
         * 4. 需要是首字母为 默认的要执行的文件前缀字符串
         * 5. 去掉已经执行过的脚本文件
         */
        List<Resource> overFilesHis = JDBResourceUtils.getResources(runningDir.getAbsolutePath());
        List<Resource> simpleFiles = JDBResourceUtils.getResources(scriptDir, this.getJdbcReleaseConfig().getFileConfig().getNameRegEx());
        /*List<File> simpleFiles = FileUtil.loopFiles(scriptDir, (fileObj) ->
        {
            for (File file : overFilesHis) {
                if (file.toURI().equals(fileObj.getName())) {
                    return false;
                }
            }
            return !fileObj.getName().equals(DefaultName.DBScriptConfig_FileName) && !fileObj.getAbsolutePath().contains(DefaultName.RUNNING_DIRNAME) && ReUtil.contains(getJdbcReleaseConfig().getFileConfig().getNameRegEx(), fileObj.getAbsolutePath()) && fileObj.getName().startsWith(getJdbcReleaseConfig().getFileConfig().getSimplePrefix());
        });*/
        Queue<Resource> thisOverFiles = new LinkedList<>();

        simpleFiles = simpleFiles.stream().filter(e -> {
            for (Resource overFilesHi : overFilesHis) {
                if (overFilesHi.getName().equals(e.getName())) {
                    return false;
                }
            }
            return !e.getName().equals(DefaultName.DBScriptConfig_FileName) && !e.getUrl().toString().contains(DefaultName.RUNNING_DIRNAME) && e.getName().startsWith(getJdbcReleaseConfig().getFileConfig().getSimplePrefix());
        }).toList();


        for (Resource resource : simpleFiles) {
            DBScriptConfig dbScriptConfig = dbScriptConfigInit.getDbScriptConfig(resource.getUrl().toURI());
            DBScriptFileService service = new DBScriptFileService(dbService, dbScriptConfig);
            log.debug("执行脚本文件: {}", resource);
            DBScriptRunModel model = service.runScript(resource, runningDir);
            if (!model.isRunStatus()) {
                String rollbackStr = getJdbcReleaseConfig().getFileConfig().getRollbackPrefix();
                String simpleStr = getJdbcReleaseConfig().getFileConfig().getSimplePrefix();
                Resource rollbackFile;

                for (Resource overFile : thisOverFiles) {
                    String rollbackFileName = rollbackStr + overFile.getName().substring(simpleStr.length());
                    String rollbackFileStr = overFile.getUrl().toString().replace(overFile.getName(), rollbackFileName);
                    rollbackFile = ResourceUtil.getResourceObj(rollbackFileStr);
                    log.debug("执行之前的回滚文件: {}", rollbackFile);
                    if (rollbackFile.getUrl() != null) {
                        service.runScript(rollbackFile, runningDir);
                    }
                }
                String rollbackFileName = rollbackStr + resource.getName().substring(simpleStr.length());
                String rollbackFileStr = resource.getUrl().toString().replace(resource.getName(), rollbackFileName);
                rollbackFile = ResourceUtil.getResourceObj(rollbackFileStr);
                log.debug("执行对标回滚文件: {}", rollbackFile);
                if (rollbackFile.getUrl() != null) {
                    service.runScript(rollbackFile, runningDir);
                }
                String errorFilePath = resource.getUrl().toString().replace(runningDir.getParentFile().toURI().toURL().toString(), runningDir.getAbsolutePath() + File.pathSeparator + DefaultName.RUNNING_ERROR_DIRNAME + File.pathSeparator);
                File errorFile = new File(errorFilePath);
                if (errorFile.exists()) {
                    FileUtil.del(errorFile);
                }
                errorFile.getParentFile().mkdirs();
                FileUtil.writeUtf8String(model.getScriptString(), errorFile);

                return false;
            } else {
                thisOverFiles.add(resource);
                // 替换为日志路径
                String logFilePath = resource.getUrl().toString().replace(runningDir.getParentFile().getAbsolutePath(), runningDir.getAbsolutePath() + File.pathSeparator + DefaultName.RUNNING_LOG_DIRNAME + File.pathSeparator);
                File logFile = new File(logFilePath);
                try {

                    if (logFile.exists()) {
                        FileUtil.del(logFile);
                    }
                    logFile.getParentFile().mkdirs();
                    boolean b = logFile.createNewFile();
                    if (!b) {
                        throw new FileSystemException("文件无法写入，请检查系统");
                    }
                } catch (IOException e) {
                    log.error(e.getMessage(), e);
                    throw new RuntimeException(e);
                }

            }
        }
        /*
         * 上面的内容相同，但 查询首字母为 每次都要执行的文件
         * 不限制已经执行过的脚本文件，虽然每次都要执行的文件会记录日志，但是筛选过程并不筛选
         */
        List<Resource> allwaysFiles = JDBResourceUtils.getResources(scriptDir, this.getJdbcReleaseConfig().getFileConfig().getNameRegEx());
        allwaysFiles = allwaysFiles.stream().filter(e -> !e.getName().equals(DefaultName.DBScriptConfig_FileName) && !StrUtil.contains(DefaultName.RUNNING_DIRNAME, e.getUrl().toString()) && e.getName().startsWith(getJdbcReleaseConfig().getFileConfig().getAllwaysPrefix())).toList();

        thisOverFiles = new LinkedList<>();
        for (Resource resource : allwaysFiles) {
            DBScriptConfig dbScriptConfig = dbScriptConfigInit.getDbScriptConfig(resource.getUrl().toURI());
            DBScriptFileService service = new DBScriptFileService(dbService, dbScriptConfig);
            log.debug("执行每次均需要执行的文件: {}", resource);
            DBScriptRunModel model = service.runScript(resource, runningDir);
            if (model.isRunStatus()) {
                thisOverFiles.add(resource);
                // 替换为日志路径
                String logFilePath = resource.getUrl().toString().replace(runningDir.getParentFile().getAbsolutePath(), runningDir.getAbsolutePath() + File.pathSeparator + DefaultName.RUNNING_LOG_DIRNAME + File.pathSeparator);
                File logFile = new File(logFilePath);
                try {
                    logFile.getParentFile().mkdirs();
                    if (logFile.exists()) {
                        FileUtil.del(logFile);
                    }
                    log.debug("将日志文件写入: {}", logFile);
                    boolean b = logFile.createNewFile();
                    if (!b) {
                        throw new FileSystemException("文件无法写入，请检查系统");
                    }
                } catch (IOException e) {
                    log.error(e.getMessage(), e);
                    throw new RuntimeException(e);
                }
            }
        }
        return true;
    }


    /**
     * 根据执行配置，将所有需要被处理的配置文件处理并执行其中的脚本
     *
     * @param model 程序运行时主函数
     */
    public void executeAll(final JDBReleaseRunModel model) throws IOException, URISyntaxException {
        model.setRunStatus(JDBReleaseRunStatus.RUNNING);
        List<String> fileDirPaths = jdbcReleaseConfig.getFileDirPaths();
        boolean runStatus;
        DataBaseService dbService = new DataBaseService(jdbcReleaseConfig.getDb());
        for (String fileDirPath : fileDirPaths) {
            runStatus = executeFile(fileDirPath, dbService);
            if (!runStatus) {
                model.setRunStatus(JDBReleaseRunStatus.ERROR);
                return;
            }
        }
        model.setRunStatus(JDBReleaseRunStatus.END);
    }

    /**
     * 根据执行配置，将所有需要被处理的配置文件处理并执行其中的脚本
     *
     * @param model      程序运行时主函数
     * @param dataSource 数据库连接池
     */
    public void executeAllByDatasource(final JDBReleaseRunModel model, DataSource dataSource) throws IOException, URISyntaxException {
        model.setRunStatus(JDBReleaseRunStatus.RUNNING);
        List<String> fileDirPaths = jdbcReleaseConfig.getFileDirPaths();
        boolean runStatus;
        DataBaseService dbService = new DataBaseService(dataSource);
        for (String fileDirPath : fileDirPaths) {
            runStatus = executeFile(fileDirPath, dbService);
            if (!runStatus) {
                model.setRunStatus(JDBReleaseRunStatus.ERROR);
                return;
            }
        }
        model.setRunStatus(JDBReleaseRunStatus.END);
    }

    public boolean executeFile(String fileDirPath, DataBaseService dbService) throws IOException, URISyntaxException {

        File runningDir = new File(this.getJdbcReleaseConfig().getLogsDirPath(), DefaultName.RUNNING_DIRNAME);
        runningDir.mkdirs();
        return execute(fileDirPath, runningDir, dbService);
    }

    public JDBReleaseConfig getJdbcReleaseConfig() {
        return jdbcReleaseConfig;
    }
}
